using System;
using System.Collections;
using System.Web.UI.WebControls;
using Sedna.Core.Common;
using Sedna.Core.Controller.Common;
using Sedna.Core.Persistence;
using Sedna.Core.Support;
using Sedna.DAO;
using Sedna.Util;
using Iesi.Collections;
using Spring.Util;

namespace Sedna.Core.Controller
{
	public class DataGridController : IDataGridController
	{
		#region Fields

        private const String DATA_PROVIDER_KEY = "dataProvider";
		private const String TRANSIENT_DATA_KEY = "additionalData";
		private const String MARKED_DATA_KEY    = "markedData";
		private const String CACHE_KEY          = "cache";
        private const String INVALID_DATA_KEY   = "invalidData";
        private const String ERRORKEY_DATA_KEY = "errorKeyDataData";
        
		private DataGrid grid;
		private String contextKey;

        private bool autoAttach;
        private IBaseDAO objectDAO;

        private String dataProviderPath;
        private String transientDataPath;
        private String markedDataPath;
        private String invalidDataPath;
        private String pageDataPath;
        private String commandPath;
        private String errorMessageKeyDataPath;

		private bool isAllowTransient;
        private bool isApplyTransientToDataSource = true;
        private bool blockInvalidPage;

		#endregion		
		
		#region Properties

        public DataGrid Grid
		{
			get { return grid;}
			set
			{
				grid = value;

				ContextKey = String.Format("context.{0}", grid.ID);

				if (!grid.Page.IsPostBack)
				{
					InitializeControllerState();
				}
			}
		}

		public bool IsAllowTransient
		{
			get { return isAllowTransient;}
			set { isAllowTransient = value;}
		}

	    public bool IsApplyTransientToDataSource
	    {
	        get{ return isApplyTransientToDataSource;}
	        set { isApplyTransientToDataSource = value; }
	    }
	    
		public String TransientDataPath
		{
			get { return transientDataPath;}
			set { transientDataPath = value;}
		}

        public String MarkedDataPath
		{
			get { return markedDataPath;}
			set { markedDataPath = value;}
		}

        public String InvalidDataPath
        {
            get { return invalidDataPath; }
            set { invalidDataPath = value; }
        }
        
	    public String DataProviderPath
	    {
	        get { return dataProviderPath;}
	        set { dataProviderPath = value;}
	    }

	    public String PageDataPath
	    {
	        get { return pageDataPath;}
	        set { pageDataPath = value;}
	    }
	    
	    public String CommandPath
	    {
	        get { return commandPath;}
	        set { commandPath = value;}
	    }

        public string ErrorMessageKeyDataPath
	    {
	        get { return errorMessageKeyDataPath; }
	        set { errorMessageKeyDataPath = value; }
	    }

	    public bool BlockInvalidPage
        {
            get { return blockInvalidPage; }
            set { blockInvalidPage = value; }
        }
        
	    public ISet TransientData
		{
			get
			{
				if (IsAllowTransient)
				{
					if (!StringUtils.IsNullOrEmpty(TransientDataPath))
					{
						return GetSetByPath(TransientDataPath);
					}

					return GetSetFromContext(TRANSIENT_DATA_KEY);
				}
				
				return null;
			}
		}

		public ISet MarkedData
		{
			get
			{
				if (IsAllowTransient)
				{
					if (!StringUtils.IsNullOrEmpty(MarkedDataPath))
					{
						return GetSetByPath(MarkedDataPath);
					}

					return GetSetFromContext(MARKED_DATA_KEY);
				}
				
				return null;
			}
		}
	   
        public ISet InvalidData
        {
            get
            {
                if (IsAllowTransient)
                {
                    if (!StringUtils.IsNullOrEmpty(InvalidDataPath))
                    {
                        return GetSetByPath(InvalidDataPath);
                    }

                    return GetSetFromContext(INVALID_DATA_KEY);
                }

                return null;
            }
        }

        public ISet PageData
        {
            get
            {
                if (!StringUtils.IsNullOrEmpty(PageDataPath))
                {
                    return GetSetByPath(PageDataPath);
                }

                return new ImmutableSet(new ListSet());
            }
        }
        
	    public IList GridSourceCache
		{
			get
			{
                if (isAllowTransient && (ControllerContext[CACHE_KEY] is IList))
				{
                    return (IList)ControllerContext[CACHE_KEY];
				}

				return null;
			}
		}

        public IDictionary ErrorMessageKeyData
        {
            get
            {
                if (IsAllowTransient)
                {
                    if (!StringUtils.IsNullOrEmpty(ErrorMessageKeyDataPath))
                    {
                        return GetDictionaryByPath(ErrorMessageKeyDataPath);
                    }

                    return GetDictionaryFromContext(ERRORKEY_DATA_KEY);
                }

                return null;
            }
        }
	    
	    public IDictionary ControllerContext
		{
			get
			{
				if (!(ExecutionContext.Current.GetScopedValue(ContextKey, Scope.Page) is IDictionary))
				{
                    ExecutionContext.Current.SetScopedValue(ContextKey, new Hashtable(), Scope.Page);					
				}

                return (IDictionary)ExecutionContext.Current.GetScopedValue(ContextKey, Scope.Page);
			}
		}

        public IDataProvider CurrentDataProvider
        {
            get { return (IDataProvider)ControllerContext[DATA_PROVIDER_KEY];}
            set { ControllerContext[DATA_PROVIDER_KEY] = value; }
        }

	    public bool AutoAttach
	    {
	        get { return autoAttach; }
	        set { autoAttach = value; }
	    }

	    public IBaseDAO ObjectDAO
	    {
	        get { return objectDAO; }
	        set { objectDAO = value; }
	    }

	    #endregion

		public void LoadOrRefresh()
		{
		    if (ControllerCommand != null)
		    {
		        ControllerCommand.execute(this);
                ClearCommand();
		    }
		    else if (NewDataProvider != null)
		    {
                LoadNewData();
		    }
            else 
		    {
                Refresh();
		    }		    			
		    
		    /* Grid won't be bound */
		}

	    public void LoadNewData()
        {
            CurrentDataProvider = (IDataProvider)NewDataProvider.Clone();
            CurrentDataProvider.StartIndex = 0;
            CurrentDataProvider.Amount = (Grid.AllowPaging ? Grid.PageSize : -1);

            if (IsAllowTransient)
            {
                TransientData.Clear();
                MarkedData.Clear();
                InvalidData.Clear();
                ErrorMessageKeyData.Clear();
            }

            Grid.CurrentPageIndex = 0;

            BindGrid(CurrentDataProvider);
        }
	    	    
	    
	    public void Refresh()
	    {
            if (CurrentDataProvider != null)
            {
                BindGrid(CurrentDataProvider);
            }
	    }

		public void Sort(String sortExpression)
		{
			Assert.IsNotNull(sortExpression, "SortExpression must be not null");
			
		    if (CurrentDataProvider != null && Validate())
			{
                CurrentDataProvider.IsAsc = !(CurrentDataProvider.IsAsc && sortExpression.Equals(CurrentDataProvider.SortExpression));
                CurrentDataProvider.SortExpression = sortExpression;								
			}

            /* Bind Grid will occur in the Grid`s Bind method */
		}

		public void LoadPage(int pageNumber)
		{
			if (Grid.AllowPaging && Validate())
            {
                Grid.CurrentPageIndex = pageNumber;

                if (CurrentDataProvider != null)
                {
                    CurrentDataProvider.StartIndex = pageNumber * Grid.PageSize;
                }
            }
		    
		    /* Bind Grid will occur in the Grid`s Bind method */
		}	    	    

		#region Private Members

		private String ContextKey
		{
			get { return contextKey;}
			set { contextKey = value;}
		}		
	    
	    private IDataProvider NewDataProvider
	    {
	        get
	        {
	            if (!StringUtils.IsNullOrEmpty(DataProviderPath))
	            {
                    return (IDataProvider)ExecutionContext.Current.GetPropertyValue(DataProviderPath);
	            }

                return null;
	        }
	    }	    	    

	    private IControllerCommand ControllerCommand
	    {
	        get
	        {
                if (!StringUtils.IsNullOrEmpty(CommandPath))
                {
                    return (IControllerCommand)ExecutionContext.Current.GetPropertyValue(CommandPath);
                }

                return null;
	        }	        
	    }
	    
	    private void ClearCommand()
	    {
            if (!StringUtils.IsNullOrEmpty(CommandPath))
            {
                ExecutionContext.Current.SetPropertyValue(CommandPath, null);
            }
	    }
	    
	    public bool Validate()
	    {
            if (IsAllowTransient && blockInvalidPage)
	        {
                ISet invalidPageEntities = PageData.Intersect(InvalidData);
                if (!invalidPageEntities.IsEmpty)
                {
                    //object objIsValid = ExecutionContext.Current[AbstractValidationAction.IS_VALID];
                    //return objIsValid == null ? true : Convert.ToBoolean(objIsValid);
                    return false;
                }
	        }
	        return true;
	    }
	    	    
	    private bool BindGrid(IDataProvider dataProvider)
	    {
            Grid.DataSource = null;
            ControllerContext[CACHE_KEY] = null;

            ICollection data;

            if (IsAllowTransient)
	        {
	            /* Bind mixed data. Items from TransientData out of intersection with provided data won't be bound */	            
	            ISet retrievedData = new ListSet(dataProvider.LoadData());
	            
	            /* Consider TransientSet implementation. 
                   Edited data will be placed on the same page but in other indexes depend from sort property */
	            data = IsApplyTransientToDataSource ?  TransientData.Intersect(retrievedData).Union(retrievedData)
                    : retrievedData;
	            /*
	            else
	            {
                    Grid.DataSource = new TransientSet(
                        TransientData.Intersect(retrievedData).Union(retrievedData), 
                        new PropertyComparer(dataProvider.SortExpression, dataProvider.IsAsc)
                        );
	            }
                */
                ControllerContext[CACHE_KEY] = Collections.ToReadOnlyList(data);	            	            
	        }
            else
            {
                /* Bind data from provider only */
                data = dataProvider.LoadData();                
            }
	        
	        Grid.DataSource = data;

            if (autoAttach && data != null)
	        {
                objectDAO.AttachToSession(data);
	        }
	        
	        //Fill marked data with new items
            if (MarkedDataPath != null && Grid.DataSource != null)
	        {
                SetSetByPath(markedDataPath, (new HashedSet((ICollection)Grid.DataSource)).Intersect(MarkedData).Union(MarkedData));
            }
	        //Fill invalid data with new items
            if (InvalidDataPath != null && Grid.DataSource != null)
	        {
                SetSetByPath(invalidDataPath, (new HashedSet((ICollection)Grid.DataSource)).Intersect(InvalidData).Union(InvalidData));
	        }
	        	        
            if (Grid.DataSource != null)
            {
                if (Grid.AllowCustomPaging)
                {
                    int totalAmount = dataProvider.TotalAmount;
                    
//                    if (totalAmount > 0)
//                    {
                        SetUpPaging(totalAmount);
//                    }
                }

                if (!StringUtils.IsNullOrEmpty(PageDataPath) && ExecutionContext.Current != null)
                {
                    ExecutionContext.Current.SetPropertyValue(PageDataPath, Collections.ToReadOnlyList((ICollection)Grid.DataSource));
                }
                
                Grid.DataBind();
            }
	        
            return (Grid.DataSource != null);
	    }

        private void SetUpPaging(IDataProvider dataProvider)
        {
            Grid.VirtualItemCount = dataProvider.TotalAmount;
            SetUpPaging();
        }

        private void SetUpPaging(int totalAmount)
        {
            Grid.VirtualItemCount = totalAmount;
            SetUpPaging();
        }

        private void SetUpPaging()
        {
            //This is useful when the last item is deleted from the last page
            int pageCount = (Grid.VirtualItemCount / Grid.PageSize) + (Grid.VirtualItemCount % Grid.PageSize > 0 ? 1 : 0);
            if (Grid.CurrentPageIndex >= pageCount)
            {
                Grid.VirtualItemCount = Grid.CurrentPageIndex * Grid.PageSize + 1;
            }
        }
	    
	    private ISet GetSetByPath(String path)
		{
			if (!(ExecutionContext.Current.GetPropertyValue(path) is ISet))
			{
                ExecutionContext.Current.SetPropertyValue(path, new ListSet());
			}

			return (ISet)ExecutionContext.Current.GetPropertyValue(path);
		}
	    
	    private bool SetSetByPath(String path, ISet set)
	    {
            if (StringUtils.IsNullOrEmpty(path))
                return false;
	        ExecutionContext.Current.SetPropertyValue(path, set);
            return true;
	    }

	    private IDictionary GetDictionaryByPath(String path)
	    {
            if (!(ExecutionContext.Current.GetPropertyValue(path) is IDictionary))
            {
                ExecutionContext.Current.SetPropertyValue(path, new Hashtable());
            }

            return (IDictionary)ExecutionContext.Current.GetPropertyValue(path);
	    }
	    
		private ISet GetSetFromContext(String key)
		{
			if (!(ControllerContext[key] is ISet))
			{
                ControllerContext[key] = new ListSet();
			}

			return (ISet)ControllerContext[key];
		}

        private IDictionary GetDictionaryFromContext(String key)
        {
            if (!(ControllerContext[key] is IDictionary))
            {
                ControllerContext[key] = new Hashtable();
            }

            return (IDictionary)ControllerContext[key];
        }

	    private void InitializeControllerState()
		{
			if (ExecutionContext.Current != null)
			{
				ExecutionContext.Current.SetScopedValue(ContextKey, new Hashtable(), Scope.Page);
			}
		}

	    public object Clone()
	    {
            return MemberwiseClone();
	    }

	    #endregion                

	}
}
